/*!	 valuenode_WPList.cpp
**	 Implementation of the "Width Point List" valuenode conversion.
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2010 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include "valuenode_wplist.h"
#include "valuenode_const.h"
#include "valuenode_composite.h"
#include "valuenode_bline.h"
#include <synfig/general.h>
#include <synfig/localization.h>
#include <synfig/valuenode_registry.h>
#include <synfig/exception.h>
#include <synfig/widthpoint.h>
#include <vector>
#include <list>

#endif

using namespace std;
using namespace etl;
using namespace synfig;

REGISTER_VALUENODE(ValueNode_WPList, RELEASE_VERSION_0_63_00, "wplist", "WPList")

ValueBase
synfig::convert_bline_to_wplist(const ValueBase& bline)
{
    // returns if the parameter is not a list or if it is a list, it is empty
    if (bline.empty()) {
        return ValueBase(type_list);
    }

    // returns if the contained type is not blinepoint
    if (bline.get_contained_type() != type_bline_point) {
        return ValueBase(type_list);
    }

    std::vector<WidthPoint> ret;
    std::vector<BLinePoint> list(bline.get_list_of(BLinePoint()));
    std::vector<BLinePoint>::const_iterator iter;
    Real position, totalpoints, i(0);
    totalpoints = (Real)list.size();

    // Inserts all the points at the positions given by the bline
    // positions are 0.0 to 1.0 equally spaced based on the number of blinepoints
    for (iter = list.begin(); iter != list.end(); ++iter) {
        position = i / totalpoints;
        ret.push_back(WidthPoint(position, iter->get_width()));
        i = i + 1.0;
    }

    // If the bline is not looped then make the cups type rounded for the
    // first and last width points.
    // In the future when this function is used to convert old blines to
    // new wlines the end and start cups should be readed from the oultline
    // layer
    if (!bline.get_loop()) {
        std::vector<WidthPoint>::iterator iter;
        iter = ret.end();
        iter--;
        iter->set_side_type_after(WidthPoint::TYPE_ROUNDED);
        iter = ret.begin();
        iter->set_side_type_before(WidthPoint::TYPE_ROUNDED);
    }

    return ValueBase(ret);
}

// synfig::widthpoint_interpolate
/*!
 * @param prev previous withpoint
 * @param next next withpoint
 * @param p position to calculate the new width between previous position and next position
 * @param smoothness [0,1] how much linear (0) or smooth (1) the interpolation is
 * @return the interpolated width
*/
Real
synfig::widthpoint_interpolate(const WidthPoint& prev, const WidthPoint& next, const Real p, const Real smoothness)
{
    // Smoothness gives linear interpolation  between
    // the result of the linear interpolation between the withpoints
    // and the interpolation based on a 5th degree polynomial that matches
    // following rules:
    // q [0,1]
    WidthPoint::SideType side_int(WidthPoint::TYPE_INTERPOLATE);
    int nsb, nsa, psb, psa;
    Real pp, np;
    Real nw, pw, rw(0.0);
    const Real epsilon(0.0000001f);
    np = next.get_position();
    pp = prev.get_position();
    nw = next.get_width();
    pw = prev.get_width();
    nsb = next.get_side_type_before();
    nsa = next.get_side_type_after();
    psb = prev.get_side_type_before();
    psa = prev.get_side_type_after();

    if (p == np) {
        return nw;
    }

    if (p == pp) {
        return pw;
    }

    // Normal case: previous position is lower than next position
    if (np > pp) {
        if (np > p && p > pp) {
            Real q;

            if (nsb != side_int) {
                nw = 0.0;
            }

            if (psa != side_int) {
                pw = 0.0;
            }

            if (np - pp < epsilon) {
                q = 0.5;
            } else {
                q = (p - pp) / (np - pp);
            }

            rw = pw + (nw - pw) * (q * (1.0 - smoothness) + q * q * q * (10 + q * (6 * q - 15)) * smoothness);
        } else if (p < pp) {
            if (psb != side_int) {
                pw = 0.0;
            }

            rw = pw;
        } else if (p > np) {
            if (nsa != side_int) {
                nw = 0.0;
            }

            rw = nw;
        }
    }
    // particular case: previous position is higher than next position
    else if (p > pp || np > p) {
        Real q(0);

        if (nsb != side_int) {
            nw = 0.0;
        }

        if (psa != side_int) {
            pw = 0.0;
        }

        if (np + 1.0 - pp < epsilon) {
            q = 0.5;
        } else {
            if (p > pp) {
                q = (p - pp) / (np + 1.0 - pp);
            }

            if (np > p) {
                q = (p + 1.0 - pp) / (np + 1.0 - pp);
            }
        }

        rw = pw + (nw - pw) * (q * (1.0 - smoothness) + q * q * q * (10 + q * (6 * q - 15)) * smoothness);
    } else if (p > np && p < pp) {
        Real q;

        if (nsa != side_int) {
            nw = 0.0;
        }

        if (psb != side_int) {
            pw = 0.0;
        }

        if (pp - np < epsilon) {
            q = 0.5;
        } else {
            q = (p - np) / (pp - np);
        }

        rw = nw + (pw - nw) * (q * (1.0 - smoothness) + q * q * q * (10 + q * (6 * q - 15)) * smoothness);
    }

    return rw;
}

ValueNode_WPList::ValueNode_WPList():
    ValueNode_DynamicList(type_width_point)
{
}

ValueNode_WPList::~ValueNode_WPList()
{
}

ValueNode_WPList*
ValueNode_WPList::create(const ValueBase &value)
{
    // if the parameter is not a list type, return null
    if (value.get_type() != type_list) {
        return NULL;
    }

    // create an empty list
    ValueNode_WPList* value_node(new ValueNode_WPList());

    // If the value parameter is not empty
    if (!value.empty()) {
        if (value.get_contained_type() == type_width_point) {
            std::vector<WidthPoint> list(value.get_list_of(WidthPoint()));
            std::vector<WidthPoint>::const_iterator iter;

            for (iter = list.begin(); iter != list.end(); iter++) {
                value_node->add(ValueNode::Handle(ValueNode_Composite::create(*iter)));
            }

            value_node->set_loop(value.get_loop());
        } else {
            // We got a list of who-knows-what. We don't have any idea
            // what to do with it.
            return NULL;
        }
    }

    return value_node;
}

ValueNode_WPList::ListEntry
ValueNode_WPList::create_list_entry(int index, Time time, Real /*origin*/)
{
    ValueNode_WPList::ListEntry ret;
    synfig::WidthPoint curr, prev, inserted;

    if (link_count()) {
        curr = (*(list[index].value_node))(time).get(curr);
        Real curr_pos(curr.get_norm_position(get_loop()));
        prev = find_prev_valid_entry_by_position(curr_pos, time);
        Real prev_pos(prev.get_norm_position(get_loop()));
        inserted.set_position((prev_pos + curr_pos) / 2);
        Real prev_width(prev.get_width());
        Real curr_width(curr.get_width());
        inserted.set_width((prev_width + curr_width) / 2);
    } else {
        inserted.set_position(0.5);
        inserted.set_width(1.0);
    }

    ret.index = 0;
    ret.set_parent_value_node(this);
    ret.value_node = ValueNode_Composite::create(inserted);
    ret.value_node->set_parent_canvas(get_parent_canvas());
    return ret;
}

ValueBase
ValueNode_WPList::operator()(Time t)const
{
    if (getenv("SYNFIG_DEBUG_VALUENODE_OPERATORS")) {
        printf("%s:%d operator()\n", __FILE__, __LINE__);
    }

    std::vector<WidthPoint> ret_list;

    std::vector<ListEntry>::const_iterator iter;
    bool rising;

    WidthPoint curr;

    // go through all the list's entries
    for (iter = list.begin(); iter != list.end(); ++iter) {
        // how 'on' is this widthpoint?
        float amount(iter->amount_at_time(t, &rising));
        assert(amount >= 0.0f);
        assert(amount <= 1.0f);
        // we store the current width point
        curr = (*iter->value_node)(t).get(curr);

        // it's fully on
        if (amount > 1.0f - 0.0000001f) {
            // push back to the returning list
            ret_list.push_back(curr);
        }
        // it's partly on
        else if (amount > 0.0f) {
            // This is where the interesting stuff happens
            Time off_time, on_time;

            if (!rising) {	// if not rising, then we were fully 'on' in the past, and will be fully 'off' in the future
                try {
                    on_time = iter->find_prev(t)->get_time();
                } catch (...) {
                    on_time = Time::begin();
                }

                try {
                    off_time = iter->find_next(t)->get_time();
                } catch (...) {
                    off_time = Time::end();
                }
            } else { // otherwise we were fully 'off' in the past, and will be fully 'on' in the future
                try {
                    off_time = iter->find_prev(t)->get_time();
                } catch (...) {
                    off_time = Time::begin();
                }

                try {
                    on_time = iter->find_next(t)->get_time();
                } catch (...) {
                    on_time = Time::end();
                }
            }

            // i_width is the interpolated width at current time given by fully 'on' surrounding width points
            Real i_width(interpolated_width(curr.get_norm_position(get_loop()), t));
            Real curr_width(curr.get_width());
            // linear interpolation by amount
            curr.set_width(i_width * (1.0 - amount) + (curr_width)*amount);
            // now insert the calculated width point into the widht list
            ret_list.push_back(curr);
        }
    }

    if (list.empty()) {
        synfig::warning(string("ValueNode_WPList::operator()():") + _("No entries in list"));
    } else if (ret_list.empty()) {
        synfig::warning(string("ValueNode_WPList::operator()():") + _("No entries in ret_list"));
    }

    return ValueBase(ret_list, get_loop());
}

String
ValueNode_WPList::link_local_name(int i)const
{
    assert(i >= 0 && (unsigned)i < list.size());
    return etl::strprintf(_("WidthPoint %03d"), i + 1);
}

LinkableValueNode*
ValueNode_WPList::create_new()const
{
    return new ValueNode_WPList();
}

bool
ValueNode_WPList::check_type(Type &type)
{
    return type == type_list;
}

synfig::WidthPoint
ValueNode_WPList::find_next_valid_entry_by_position(Real position, Time time)const
{
    std::vector<ListEntry>::const_iterator iter;
    Real next_pos(1.0);
    synfig::WidthPoint curr, next_ret(next_pos, 0.0);

    for (iter = list.begin(); iter != list.end(); ++iter) {
        curr = (*iter->value_node)(time).get(curr);
        Real curr_pos(curr.get_norm_position(get_loop()));
        bool status((*iter).status_at_time(time));

        if ((curr_pos > position) && (curr_pos < next_pos) && status) {
            next_pos = curr_pos;
            next_ret = curr;
        }
    }

    return next_ret;
}

synfig::WidthPoint
ValueNode_WPList::find_prev_valid_entry_by_position(Real position, Time time)const
{
    std::vector<ListEntry>::const_iterator iter;
    Real prev_pos(-123456.0);
    synfig::WidthPoint curr, prev_ret(prev_pos, 0.0);

    if (!list.size()) {
        return prev_ret;
    }

    for (iter = list.begin(); iter != list.end(); ++iter) {
        curr = (*iter->value_node)(time).get(curr);
        Real curr_pos(curr.get_norm_position(get_loop()));
        bool status((*iter).status_at_time(time));

        if ((curr_pos < position) && (curr_pos > prev_pos) && status) {
            prev_pos = curr_pos;
            prev_ret = curr;
        }
    }

    if (prev_ret.get_position() == -123456.0)
        // This means that no previous has been found.
        // Let's consider if the bline is looped.
    {
        bool blineloop(ValueNode_BLine::Handle::cast_dynamic(get_bline())->get_loop());

        if (blineloop) {
            prev_ret = find_prev_valid_entry_by_position(2.0, time);
        } else {
            prev_ret = find_next_valid_entry_by_position(-1.0, time);
            prev_ret.set_position(0.0);
        }

    }

    return prev_ret;
}

Real
ValueNode_WPList::interpolated_width(Real position, Time time)const
{
    synfig::WidthPoint prev, next;
    prev = find_prev_valid_entry_by_position(position, time);
    next = find_next_valid_entry_by_position(position, time);
    prev.normalize(get_loop());
    next.normalize(get_loop());
    return widthpoint_interpolate(prev, next, position);
}

ValueNode::LooseHandle
ValueNode_WPList::get_bline()const
{
    return bline_;
}

void
ValueNode_WPList::set_bline(ValueNode::Handle b)
{
    bline_ = b;
}